# -*- coding: utf-8 -*-
__author__ = 'gerry'

'''
朴素贝叶斯分类：计算出每个样本在各个类别中的概率，然后划分类别
    优点：在数据较少的情况下仍然有效，可以处理多类别问题
    缺点：对于输入数据的准备方式较为敏感
    适合数据类型：标称型数据
    贝叶斯准则：计算条件概率的方法
        已知P(x|c),求P(c|x)
            p(c|x) = p(x|c)P(c)/p(x)
        对于某个点（x,y）来说
            p(ci|x.y) = p(x,y|ci)p(ci)/p(x,y)
    机器学习的一个重要应用就是文档（新闻报道、用户留言、政府公文）的自动分类
'''
'''
    朴素贝叶斯的一般过程：
        * 收集数据：可以使用任何方法，这里使用RSS源
        * 准备数据：需要数值型数据或者布尔型数据
        * 分析数据：有大量特征时，绘制特征的作用不大，此时的直方图的效果更好
        * 训练算法：计算不同的独立特征的条件概率
        * 测试算法：计算错误概率
        * 使用算法：一个常见的朴素贝叶斯应用就是文档的分类。

'''
'''
    选取特征工程：
        根据统计学可以知道，如果每个特征需要N个样本，那么10个特征将需要N的10次方个样本，对于1000个特征，可想而知，这样所需要的样本数会随着特征数目增大而迅速增长。
        但是，如果特征之间相互独立，那么样本数就可以从N^1000减少到N*1000
        朴素贝叶斯假设：假设各个样本之间相互独立
'''

'''
    Created on Nov 9,2017
    @aurhor:Gerry
'''
from numpy import *


'''
    数据准备:判断一个词是否在一个文章当中
'''
# 词表到向量的转换函数

def loadDataSet():
    postingList = [
        ['my', 'dog', 'has', 'flea', 'problems', 'help', 'please'],
        ['maybe', 'not', 'take', 'him', 'to', 'dog', 'park', 'stupid'],
        ['my', 'dalmation', 'is', 'so', 'cute', 'I', 'love', 'him'],
        ['stop', 'posting', 'stupid', 'worthless', 'garbage'],
        ['mr', 'licks', 'ate', 'my', 'steak', 'how', 'to', 'stop', 'him'],
        ['quit', 'buying', 'worthless', 'dog', 'food', 'stupid']
    ]

    classVec = [0,1,0,1,0,1]    #1 is abusive（侮辱性语言），0 is not, category labels
    return postingList,classVec
#创建一个包含在所有文档中出现的不重复词的列表（选用set集合）
def createVocabList(dataSet):
    vocabSet = set([])  #create empty set
    for document in dataSet:
        vocabSet = vocabSet|set(document)   #union of the two sets
    return list(vocabSet)

def setOfWords2Vec(vocabList,imputSet):
    returnVec = [0]*len(vocabList)
    for word in imputSet:
        if word in vocabList:
            returnVec[vocabList.index(word)] = 1
        else:
            print "the word:%s  is not in my Vocabulary!" %word
    return returnVec


'''
    如何计算概率：
        计算每个类别中的文档数目
        对每篇训练文档：
            对每个类别：
                如果词条中出现文档中-->增加该词条的计数值
                增加所有词条的计数值
            对于每个类别：
                对每个词条：
                    将该词条的数据除以总词条数目得到条件概率
            返回每个类别的条件概率

'''
'''
    使用上述计算的数字计算概率
'''

def trainNB0(trainMatrix,trainCategory):
    # trainMatrix:是文档矩阵
    # trainCategory:由每篇文章类别标签所构成的向量
    numTrainDocs = len(trainMatrix)#矩阵的行（即样本数->文章个数）
    numWords = len(trainMatrix[0]) # 每篇文章的单词数
    pAbusive = sum(trainCategory)/float(numTrainDocs)
    #计算p(wi|c1),p(wi|c2),初始化程序中的分子变量和分母变量
    p0Num = ones(numWords); p1Num = ones(numWords) #change to ones()
    p0Denom = 2.0;p1Denom = 2.0   #change to 2.0

    #利用贝叶斯分类器对文档进行分类时，要计算多个概率的乘积以获得文档属于某个类别的概率
    #即计算p(w0|1)p(w1|1)p(w1|1).如果其中一个概率为0，那么最后的乘积也为0，为降低这种影响，可以将所有词出现数初始化为1，并将分母初始化为2
    for i in range(numTrainDocs):
        if trainCategory[i] ==1:
            p1Num+=trainMatrix[i]
            p1Denom +=sum(trainMatrix[i])
        else:
            p0Num +=trainMatrix[i]
            p0Denom+=sum(trainMatrix[i])
    p1Vect = log(p1Num/p1Denom) #change to log
    p0Vect = log(p0Num/p0Denom)   #change to log
    #下溢出：这是由于有太多的很小的数相乘造成的p(w0|ci)p(w1|ci)p(w2|ci)...p(wN|ci)时，由于大部分因子都非常小，所以程序会下溢或者得不到正确的答案，因此采取自然对数

    return p0Vect,p1Vect,pAbusive


# 朴素贝叶斯分类函数

def classifyNB(vec2Classify,p0Vec,p1Vec,pClass1):
    p1 =sum(vec2Classify*p1Vec)+log(pClass1)    #元素相乘
    p0 = sum(vec2Classify*p0Vec)+log(1.0-pClass1)
    print 'p1 = ',p1
    print 'p1 = ',p0
    if p1>p0:
        return 1
    else:
        return 0
# 测试案例
def testingNB():
    listOPosts,listClasses = loadDataSet() # 产生训练数据集以及类别标签
    myVocablist = createVocabList(listOPosts)   #使用集合的方法对训练数据进行去重
    trainMat = []   #产生训练数据矩阵
    for postinDoc in listOPosts:    #按行进行取值
        trainMat.append(setOfWords2Vec(myVocablist,postinDoc))
    p0V,p1V,pAb = trainNB0(array(trainMat),array(listClasses))
    testEntry = ['love','my','dalmation']
    thisDoc = array(setOfWords2Vec(myVocablist,testEntry))
    print testEntry,'classified as',classifyNB(thisDoc,p0V,p1V,pAb)
    testEntry = ['stupid','farbage']
    thisDoc = array(setOfWords2Vec(myVocablist,testEntry))
    print testEntry,'classified as',classifyNB(thisDoc,p0V,p1V,pAb)

'''
================================================================================================
电子邮件分类
    * 收集数据：提供文本文件
    * 准备数据：将文本文件解析成词条向量
    * 分析数据：检查词条确保解析的正确性
    * 训练算法：使用我们之前简历的trainNB0()函数
    * 测试算法：使用classifyNB(),并且构建一个新的测试函数来计算文档集的错误率
    * 使用算法：构建一个完整的程序对一组文档进行分类，将错误的文档输出到屏幕上
'''

#切分词组；接受一个大字符串并将其解析为字符串列表，该函数去掉少于两个字符的字符串，并将所有字符串转换为小写
def textParse(bigString):
    import re
    listOfTakens = re.split(r'\w*',bigString)
    return [tok.lower() for tok in listOfTakens if len(tok)>2]

# 对贝叶斯垃圾邮件分类器进行自动化处理

def spamTest():
    docList =[];classList = [];fullText =[]
    for i in range(1,26):
        wordList = textParse(open('emil/spam/%d.txt' %i).read())
        docList.append(wordList)
        fullText.extend(wordList)












if __name__ == '__main__':
   testingNB()

















































